﻿
namespace Patterns4Net.Tests.Core.CecilExtensions
{
    using System;
    using System.Linq;
    using Mono.Cecil;
    using NUnit.Framework;
    using Patterns4Net.Core.CecilExtensions;

    [TestFixture]
    public class MethodExtentionsCallGraphMethodsTest
    {
        private static int fooStaticField;

        private double fooField;

        [Test]
        public void GetFieldsAccessesInAssignFields()
        {
            var method = GetMethod("AssignFields");
            var accesses = method.GetFieldAccesses();

            var fieldNames = accesses.Select(f => f.Field.Name).ToArray();
            CollectionAssert.AreEquivalent(
                new[] { "fooField", "fooStaticField" },
                fieldNames,
                string.Format("Result: {0}", string.Join(",", fieldNames)));

            var foo = accesses.First(f => f.Field.Name == "fooField");
            Assert.IsTrue(foo.IsWrite);
            Assert.IsTrue(foo.AssignedValue.IsParameter);
            Assert.AreEqual(0, foo.AssignedValue.ParameterIndex);

            var fooStatic = accesses.First(f => f.Field.Name == "fooStaticField");
            Assert.IsTrue(fooStatic.IsWrite);
            Assert.IsTrue(fooStatic.AssignedValue.IsParameter);
            Assert.AreEqual(1, fooStatic.AssignedValue.ParameterIndex);
        }

        [Test]
        public void GetFieldAccessesInReturnFieldsSum()
        {
            var method = GetMethod("ReturnFieldsSum");
            var accesses = method.GetFieldAccesses();

            var fieldNames = accesses.Select(f => f.Field.Name).ToArray();
            CollectionAssert.AreEquivalent(
                new[] { "fooField", "fooStaticField" },
                fieldNames,
                string.Format("Result: {0}", string.Join(",", fieldNames)));

            var foo = accesses.First(f => f.Field.Name == "fooField");
            Assert.IsTrue(foo.IsRead);

            var fooStatic = accesses.First(f => f.Field.Name == "fooStaticField");
            Assert.IsTrue(fooStatic.IsRead);
        }

        [Test]
        public void GetMethodCallsInCallToString()
        {
            var method = GetMethod("CallToString");
            var methodCalls = method.GetMethodCalls();

            var methodNames = methodCalls.Select(m => m.Method.Name).ToArray();
            CollectionAssert.AreEquivalent(
                new[] { "WriteLine", "ToString" }, 
                methodNames,
                string.Format("Result: {0}", string.Join(",", methodNames)));

            var writeLine = methodCalls.First(m => m.Method.Name == "WriteLine");
            Assert.IsFalse(writeLine.HasTargetObject);
            Assert.Throws<InvalidOperationException>(() => writeLine.TargetObject.Equals(null));
            Assert.AreEqual(1, writeLine.Parameters.Length);

            var toString = methodCalls.First(m => m.Method.Name == "ToString");
            Assert.IsNotNull(toString.TargetObject);
            Assert.IsTrue(toString.TargetObject.IsThisParameter);
        }

        [Test]
        public void GetMethodCallsInCallToStringOnParam()
        {
            var method = GetMethod("CallToStringOnParam");
            var methodCalls = method.GetMethodCalls();

            var methodNames = methodCalls.Select(m => m.Method.Name).ToArray();
            CollectionAssert.AreEquivalent(
                new[] { "WriteLine", "ToString" },
                methodNames,
                string.Format("Result: {0}", string.Join(",", methodNames)));

            var writeLine = methodCalls.First(m => m.Method.Name == "WriteLine");
            Assert.IsFalse(writeLine.HasTargetObject);
            Assert.Throws<InvalidOperationException>(() => writeLine.TargetObject.Equals(null));
            Assert.AreEqual(1, writeLine.Parameters.Length);

            var toString = methodCalls.First(m => m.Method.Name == "ToString");
            Assert.IsNotNull(toString.TargetObject);
            Assert.IsTrue(toString.TargetObject.IsParameter);
            Assert.AreEqual(0, toString.TargetObject.ParameterIndex);
        }

        [Test]
        public void GetConstructorCallsInGetDate()
        {
            var method = GetMethod("GetDate");
            var ctorCalls = method.GetConstructorCalls();

            Assert.AreEqual(1, ctorCalls.Count());
            Assert.AreEqual("DateTime", ctorCalls.First().Type.Name);
            Assert.AreEqual(3, ctorCalls.First().Parameters.Length);
            Assert.IsTrue(ctorCalls.First().Parameters[0].IsParameter);
            Assert.AreEqual(0, ctorCalls.First().Parameters[0].ParameterIndex);
        }

        private static MethodDefinition GetMethod(string name)
        {
            return typeof(MethodExtentionsCallGraphMethodsTest).GetTypeDefinition().GetMethod(name).Resolve();
        }

        private void AssignFields(double x, int y)
        {
            this.fooField = x;
            fooStaticField = y;
        }

        private double ReturnFieldsSum()
        {
            return this.fooField + fooStaticField;
        }

        private void CallToString()
        {
            Console.WriteLine(this.ToString());
        }

        private void CallToStringOnParam(object obj)
        {
            Console.WriteLine(obj.ToString());
        }

        private DateTime GetDate(int year)
        {
            return new DateTime(year, 1, 1);
        }
    }
}
